/*******************************************************************************
 * Copyright (c) 2006 CEA List.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     CEA List - initial API and implementation
 *******************************************************************************/
package com.cea.papyrus.core.ui.dialogs;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.eclipse.emf.common.util.EList;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.uml2.uml.Classifier;
import org.eclipse.uml2.uml.Element;
import org.eclipse.uml2.uml.InstanceSpecification;

import com.cea.papyrus.core.PapyrusPlugin;
import com.cea.papyrus.core.ui.dialogs.ChooseSetUseCasesDialog.UseCaseLabelProvider;
import com.cea.papyrus.profile.utils.Util;

/**
 * Dialog to choose a set of classifiers
 */
public class ChooseSetClassifiersDialog extends ChooseSetAssistedDialog implements IChooseDialog {

	/** LabelProvider for stereotype completion proposal provider with qualified names. */
	final private ClassifierQualifiedLabelProvider qualifiedLabelProvider = new ClassifierQualifiedLabelProvider();
	
	/** old list of applied stereotypes */ 
	final private EList oldClassifiers;
	
	/**  the uml element on which modifications occurs */
	final private Element umlElement;
	
	/** boolean that indicates if modifications are made to the umlElement. */
	private boolean hasChanged = false; 
	
	/**
	 * Creates a new ChooseSetClassifiersDialog.
	 * @param parentShell the parent shell
	 * @param theElement the UML element to be modified
	 */
	public ChooseSetClassifiersDialog(Shell parentShell, Element theElement){
		super(parentShell,"Possible Classifiers: ","InstanceSpecification's Classifiers: ");
		umlElement = theElement;
		
		labelProvider = new ClassifierLabelProvider();
		decoratedContentProposalProvider = new ClassifierContentProposalProvider();
	
		// save old list of extension points (used to compare when ok is pressed)
		oldClassifiers = ((InstanceSpecification)theElement).getClassifiers();
		
		Iterator<Classifier> classifier = oldClassifiers.iterator();
		while (classifier.hasNext()){
			selectedElementList.addElement(classifier.next());
		}
		
		// build possible list
		ArrayList<org.eclipse.uml2.uml.Package> traversedPackage = new ArrayList<org.eclipse.uml2.uml.Package>();
		ArrayList<Classifier> possibleClassifiers = new ArrayList<Classifier>();
		getAllClassifiers(Util.topPackage(theElement),possibleClassifiers,traversedPackage);
		classifier = possibleClassifiers.iterator();
		while (classifier.hasNext()){
			Classifier current = (Classifier)classifier.next();
			if(!selectedElementList.contains(current)) {
				possibleElementList.addElement(current);
			}
		}
	}
	
	/**
	 * Gets the all Classifiers.
	 * @param possibleClassifiers 
	 * @param possibleUseCases the possible use cases
	 * @param thepackage the thepackage
	 * @param traversedPackage the traversed package
	 */
	private void getAllClassifiers(org.eclipse.uml2.uml.Package thepackage, ArrayList<Classifier> possibleClassifiers, ArrayList<org.eclipse.uml2.uml.Package> traversedPackage) {
		traversedPackage.add(thepackage);
		Iterator elements = thepackage.getMembers().iterator();
		while (elements.hasNext()){
			Element element = (Element) elements.next();
			if ((element instanceof Classifier) && (element != umlElement) && (possibleClassifiers.contains((Classifier)element) == false)) {
				possibleClassifiers.add((Classifier)element);
			}else if ((element instanceof org.eclipse.uml2.uml.Package) && (traversedPackage.contains((org.eclipse.uml2.uml.Package)element) == false)){
				getAllClassifiers((org.eclipse.uml2.uml.Package)element,possibleClassifiers,traversedPackage);
			}
		}
	}
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	protected Control createDialogArea(Composite parent) {
		Control composite = super.createDialogArea(parent);
		
		// Add 2 columns to the table area
		// possibleElementsTable.setLinesVisible(true);
		possibleElementsTable.setHeaderVisible(true);

		// 1st column with image/checkboxes - NOTE: The SWT.CENTER has no effect!!
		TableColumn column = new TableColumn(possibleElementsTable, SWT.CENTER, 0);		
		column.setText("Classifier");
		column.setWidth(150);
		column.addSelectionListener(new SelectionAdapter() {
	       	
			public void widgetSelected(SelectionEvent e) {
				possibleElementsTableViewer.setSorter(new AlphabeticalViewerSorter(0));
			}
		});
		
		// 2nd column with task Description
		column = new TableColumn(possibleElementsTable, SWT.LEFT, 1);
		column.setText("Information");
		column.setWidth(165);
		// Add listener to column so tasks are sorted by description when clicked 
		column.addSelectionListener(new SelectionAdapter() {
       	
			public void widgetSelected(SelectionEvent e) {
				possibleElementsTableViewer.setSorter(new AlphabeticalViewerSorter(1));
			}
		});

		// set sorter to the possible element table viewer 
		possibleElementsTableViewer.setSorter(new AlphabeticalViewerSorter(0));
		
		return composite;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	protected void runAddElement(String name) {
		// find the extension point in the list
		Classifier classifier =null;
		Iterator<Classifier> it = possibleElementList.getElements().iterator();
		while (it.hasNext()) {
			Classifier element = (Classifier) it.next();
			if( name.equalsIgnoreCase(element.getName()) || name.equalsIgnoreCase(element.getQualifiedName())) {
				classifier = element;
			}
		}
		if(classifier != null) {
			runActionAdd(classifier);
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	protected boolean isSelectableElement(String text) {
		// iterate through all possibilities and return true if text corresponds
		Iterator<Classifier> it = possibleElementList.getElements().iterator();
		while (it.hasNext()) {
			Classifier element = (Classifier) it.next();
			if( text.equalsIgnoreCase(element.getName()) || text.equalsIgnoreCase(element.getQualifiedName())) {
				return true;
			}
		}
		return false;
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.jface.dialogs.Dialog#okPressed()
	 */
	/**
	 * Ok pressed.
	 */
	protected void okPressed() {
		int modifCount = 0;
		
		// apply new extension point (diff old and new List)
		modifCount = addClassifiers();
		
		// removed unused extension points
		modifCount += removeClassifiers();
		
		// order the list of select extension points
		modifCount += orderClassifiers();
		
		// element has changed if there is a modification or more
		hasChanged = (modifCount > 0);
		
		super.okPressed();
	}

	/**
	 * Adds the use cases.
	 * 
	 * @return the int
	 */
	private int addClassifiers() {
		int modifCount=0;	// count number of creations (to force refresh as dialog is closed)
		// for all element of the new List that is not in the old list, add the extension point in the extension location list
		Iterator newElements = selectedElementList.getElements().iterator();
		while(newElements.hasNext()) {
			Classifier classifier = (Classifier) newElements.next();
			if(!oldClassifiers.contains(classifier)) {
				//classifier.getSubjects().add((Classifier)umlElement);
				oldClassifiers.add(classifier);
				modifCount++;
			}
		}
		return modifCount;
	}
	
	/**
	 * Removes the use cases.
	 * @return the int
	 */
	private int removeClassifiers() {
		int modifCount=0;	// count number of destructions (to force refresh as dialog is closed)
		// for all element of the old List that is not in the new list, remove the extension 
		// point from the extension location list
		ArrayList<Classifier> elementsToDestroy= new ArrayList<Classifier>();
		Iterator oldElements = oldClassifiers.iterator();
		while(oldElements.hasNext()) {
			Classifier classifier = (Classifier) oldElements.next();
			if(!selectedElementList.getElements().contains(classifier)) {
				elementsToDestroy.add(classifier);
				modifCount++;
			}
		}	
		// apply modifications
		Iterator elementToDestroy = elementsToDestroy.iterator();
		while(elementToDestroy.hasNext()) {
			Classifier classifier = (Classifier) elementToDestroy.next();
			//classifier.getSubjects().remove((Classifier)umlElement);
			oldClassifiers.remove(classifier);
		}
		return modifCount;
	}

	/**
	 * Order use cases.
	 * @return the int
	 */
	private int orderClassifiers() {
		int modifCount=0;	// count number of destructions (to force refresh as dialog is closed)	
		// for all element of the old List that is not in the new list, remove the extension 
		// point from the extension location list
		Map<Classifier, Integer> elementsToMove = new HashMap<Classifier, Integer>();
		Iterator it = oldClassifiers.iterator();
		while(it.hasNext()) {
			// position of the extension point in the new list
			Classifier classifier = (Classifier) it.next();
			int newIndex = selectedElementList.getElements().indexOf(classifier);
			if(oldClassifiers.indexOf(classifier) != newIndex) {
				// extension point should be moved
				elementsToMove.put(classifier, new Integer(newIndex));
				modifCount++;
			}
		}
		// apply modifications
		Iterator elementToMove = elementsToMove.keySet().iterator();
		while(elementToMove.hasNext()) {
			Classifier classifier = (Classifier) elementToMove.next();
			oldClassifiers.move(((Integer)(elementsToMove.get(classifier))).intValue(), classifier);;
		}
		return modifCount;
	}


	/**
	 * Content Proposal provider for extension points dialog. Propose the simple
	 * name of the extension point and its qualified name.
	 */
	public class ClassifierContentProposalProvider extends DecoratedContentProposalProvider {

		/**
		 * {@inheritDoc}
		 */
		@Override
		public DecoratedContentProposal[] getProposals(String contents, int position) {
			ArrayList<DecoratedContentProposal> proposals = new ArrayList<DecoratedContentProposal>();

			if(possibleElementList != null) {
				Iterator it = possibleElementList.getElements().iterator();
				while(it.hasNext()) {
					final Classifier classifier = (Classifier)it.next();
					final String simpleName = ( (classifier.getName()!=null) ? classifier.getName() : "" ) ;
					final String qualifiedName = ( (classifier.getQualifiedName()!=null) ? classifier.getQualifiedName() : "" );

					if( position < simpleName.length() && contents.substring(0, position).equalsIgnoreCase(simpleName.substring(0, position))) {
						proposals.add(new DecoratedContentProposal(classifier, labelProvider));
					}

					if( position < qualifiedName.length() && contents.substring(0, position).equalsIgnoreCase(qualifiedName.substring(0, position))) {
						proposals.add(new DecoratedContentProposal(classifier, qualifiedLabelProvider));
					}
				}
			}
			
			Collections.sort(proposals);
			return proposals.toArray(new DecoratedContentProposal[proposals.size()]);
		}
	}

	/**
	 * Label provider for extension points.
	 */
	protected class ClassifierLabelProvider extends LabelProvider implements ITableLabelProvider {

		/**
		 * {@inheritDoc}
		 */
		@Override
		public Image getImage(Object element) {
			Image image = null;
			if((element instanceof Classifier)) {
				image = PapyrusPlugin.UML_LABEL_PROVIDER.getImage((Classifier)element);
			}
			// return the image if not null or the default stereotype image if stereotype has no image
			return (image != null) ? image : PapyrusPlugin.UML_LABEL_PROVIDER.getImage(element) ;
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public String getText(Object classifier) {
			if(!(classifier instanceof Classifier)) {
				return "not a classifier";
			} else {
				final String simpleName = ((Classifier)classifier).getName();
				if(simpleName==null) {
					return "";
				} else {
					return simpleName;
				}
			}
		}

		/**
		 * {@inheritDoc}
		 */
		public Image getColumnImage(Object element, int columnIndex) {
			if(columnIndex == 0) {
				return getImage(element);
			}
			return null;
		}

		/**
		 * {@inheritDoc}
		 */
		public String getColumnText(Object element, int columnIndex) {
			if(columnIndex == 0) {
				return getText(element);
			} else if(columnIndex == 1) {
				if(!(element instanceof Classifier)) {
					return "not a classifier";
				} else {
					final String qualifiedName = ((Classifier)element).getQualifiedName();
					if(qualifiedName==null) {
						return "";
					} else {
						return qualifiedName;
					}
				}
			}
			return "not the right column index";
		}
	}

	/**
	 * Label provider for stereotypes that modifies {@link UseCaseLabelProvider#getText(Object)}.
	 * It returns the qualified name of the extension point instead of the simple name.
	 */
	protected class ClassifierQualifiedLabelProvider extends ClassifierLabelProvider {
		
		/**
		 * {@inheritDoc}
		 */
		@Override
		public String getText(Object classifier) {
			if(!(classifier instanceof Classifier)) {
				return "not a classifier";
			} else {
				final String qualifiedName = ((Classifier)classifier).getQualifiedName();
				if(qualifiedName==null) {
					return "";
				} else {
					return qualifiedName;
				}
			}
		}
	}

	/**
	 * returns <code>true</code> if uml element has changed.
	 * @return <code>true</code> if uml element has changed
	 */
	public boolean hasElementChanged() {
		return hasChanged;
	}
	
}
